/*
 * File:                StandardPluginSource.cs
 * Authors:             Jonathan T. McClain, Travis Bauer
 * Company:             Sandia National Laboratories
 * Project:             MVC
 *
 * Copyright May 6, 2008, Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
 * or on behalf of the U.S. Government. Export of this program may require a
 * license from the United States Government. See CopyrightHistory.txt for
 * complete details.
 *
 * Reviewers:
 * Review Date:
 * Review Comments:
 *
 * Revision History:
 *
 * $Log: StandardPluginSource.cs,v $
 * Revision 1.11  2008/05/06 22:16:04  jtmccl
 * 1. Added an application setting called ErrorDialogPriorityThreshold and
 * updated ErrorDialogTraceListener to only display errors above this
 * threshold.
 * 2. Modified StandardPluginSource to send an Error with priority 50 when
 * appdomain_AssemblyResolve is unable to locate an assembly. This is
 * because it appears to be possible for an assembly to be resolved after
 * this event, making the error less important.
 *
 */

using Sandia.MVCFramework.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security;
using System.Text;

namespace Sandia.MVCFramework.Plugin
{
    /// <summary>
    /// A standard plugin source class. This plugin source obtains plugins by
    /// searching the given directory's subdirectories.
    /// </summary>
    public class StandardPluginSource : IPluginSource
    {
        /// <summary>
        /// The directory to get the plugins from.
        /// </summary>
        private string pluginDirectory;

        /// <summary>
        /// The loaded plugins.
        /// </summary>
        private List<IPlugin> availablePlugins = null;

        /// <summary>
        /// The directory to get the plugins from.
        /// </summary>
        public string PluginDirectory
        {
            get
            {
                return this.pluginDirectory;
            }
            set
            {
                this.pluginDirectory = value;
            }
        }

        /// <summary>
        /// Constructor for StandardPluginSource.
        /// </summary>
        /// <param name="pluginDirectory">
        /// The directory to get the plugins from.
        /// </param>
        public StandardPluginSource(
            string pluginDirectory)
        {
            this.pluginDirectory = pluginDirectory;
        }

        /// <summary>
        /// Gets a list of plugins in the given directory.
        /// </summary>
        /// <param name="pluginDirectory">The directory to search.</param>
        /// <returns>The list of plugins.</returns>
        private List<IPlugin> getPlugins(
            string pluginDirectory)
        {
            if(Directory.Exists(pluginDirectory))
            {
                AppDomain appDomain = AppDomain.CurrentDomain;
                appDomain.AssemblyResolve += 
                    new ResolveEventHandler(appDomain_AssemblyResolve);

                List<IPlugin> plugins = new List<IPlugin>();
                try
                {
                    DirectoryInfo dirInfo = new DirectoryInfo(pluginDirectory);
                    // Loop through each subdirectory in the plugin directory.
                    foreach (DirectoryInfo pluginDirInfo in dirInfo.GetDirectories())
                    {
                        // Loop through each DLL in the subdirectory.
                        foreach (FileInfo fileInfo in pluginDirInfo.GetFiles("*.dll"))
                        {
                            try
                            {
                                // Load the DLL assembly and initialize it
                                Assembly assembly = Assembly.LoadFrom(fileInfo.FullName);
                                // Loop through each of the types in the assembly and
                                // check to see if it is a plugin.
                                foreach (Type assemblyType in assembly.GetTypes())
                                {
                                    // If it is a plugin...
                                    if (typeof(IPlugin).IsAssignableFrom(assemblyType))
                                    {
                                        // Get the plugin empty constructor.
                                        ConstructorInfo pluginConstructorInfo =
                                            assemblyType.GetConstructor(Type.EmptyTypes);
                                        // If a constructor was found.
                                        if (pluginConstructorInfo != null)
                                        {
                                            // Construct the plugin.
                                            IPlugin plugin =
                                                (IPlugin)pluginConstructorInfo.Invoke(
                                                    new object[0]);
                                            // Add the plugin.
                                            plugins.Add(plugin);
                                        }
                                    }
                                }
                            }
                            catch (ReflectionTypeLoadException e)
                            {
                                string message = e.Message;
                                foreach (Exception loaderException in e.LoaderExceptions)
                                {
                                    message = message + "\n" + loaderException.Message;
                                }

                                // Generate a log entry
                                LoggingTools.SendToLog(
                                    "getPlugins: ReflectionTypeLoadException - " + message + " thrown for " + fileInfo.FullName,
                                    LoggingCategories.GENERAL,
                                    50);
                            }
                            catch (BadImageFormatException)
                            {
                                // Generate a log entry
                                LoggingTools.SendToLog(
                                    "getPlugins: BadImageFormatException thrown for " + fileInfo.FullName,
                                    LoggingCategories.GENERAL,
                                    50);
                            }
                        }
                    }
                }
                catch (SecurityException e)
                {
                    LoggingTools.SendToLog(
                        "getPlugins: User does not have access priviliges for the plug-in source directory. \n" + e.Message,
                        LoggingCategories.ERROR,
                        100);
                }
                return plugins;
            }
            else
            {
                // Generate a log entry
                LoggingTools.SendToLog(
                    "Plug-in source directory not found!",
                    LoggingCategories.ERROR,
                    100);
                return null;
            }
        }

        /// <summary>
        /// This event represents a last-ditch effort to resolve an assembly.
        /// This event is fired after the GAC and executing directory are 
        /// checked. We use this event to walk the subdirectories in the plugin
        /// directory and check for the desired assembly. Where duplicates exist,
        /// the first match found is used.
        /// </summary>
        /// <param name="sender">The AppDomain.</param>
        /// <param name="args">The event arguments.</param>
        /// <returns>The resolved assembly.</returns>
        Assembly appDomain_AssemblyResolve(
            object sender, 
            ResolveEventArgs args)
        {
            // Get the assembly name.
            int endOfAssemblyName = args.Name.IndexOf(",");
            if(endOfAssemblyName == -1)
            {
                endOfAssemblyName = args.Name.Length;
            }
            string assemblyName = args.Name.Substring(0, endOfAssemblyName) + ".dll";
            // Walk the plugin directory's subdirectories.
            DirectoryInfo dirInfo = new DirectoryInfo(this.PluginDirectory);
            foreach (DirectoryInfo pluginDirInfo in dirInfo.GetDirectories())
            {
                string whereItMightBe = 
                    Path.Combine(
                        pluginDirInfo.FullName, 
                        args.Name.Substring(0, endOfAssemblyName)) + ".dll";
                // If the assembly is in this directory, load it and return.
                if (File.Exists(whereItMightBe))
                {
                    Assembly answer = Assembly.LoadFile(whereItMightBe);
                    return answer;
                }
            }
            // The assembly was not found.
            // Generate a log entry
            LoggingTools.SendToLog(
                "appDomain_AssemblyResolve: Unable to load a dependant assembly: " + args.Name,
                LoggingCategories.ERROR,
                50);

            return null;
        }

        #region IPluginSource Members

        /// <summary>
        /// Gets a list of the available plugins from this source.
        /// </summary>
        /// <returns>The list of plugins.</returns>
        public IList<IPlugin> AvailablePlugins
        {
            get
            {
                if (this.availablePlugins == null)
                {
                    this.availablePlugins = 
                        this.getPlugins(this.pluginDirectory);
                }
                return this.availablePlugins;
            }
        }

        #endregion
    }
}
